package proxy;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


/**
 * 动态代理
 * 可以代理任何接口的不仅仅局限与Move这个接口
 * 比如记录时间的 任何一个对象的方法都可能需要记录时间做性能测试的。
 * 让 LogProxy 和 TimeProxy这两个代理可以作用于任意对象。 如果任意带来牵扯到一个问题不知道代理哪个方法。
 * 所以需要动态代理需要用invoke方法，动态代理生成中间的代理类。 proxy0 中间代理类 它去实现你希望的接口 invoke 调用接口的方法。
 */
public class DynamicProxy {
    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println(new Tank().getClass() == Tank.class);

        ClassLoader loader = ClassLoader.getSystemClassLoader();
        try {
            Tank tank = (Tank) loader.loadClass("proxy.Tank").getConstructor().newInstance();
            tank.move();
            System.out.println("testing");
            System.out.println(tank.getClass().getClassLoader().getResourceAsStream("testfile.txt"));
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(tank.getClass().getClassLoader().getResourceAsStream("testfile.txt")));
            System.out.println(bufferedReader.read());

        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


        // 从源码中得知，设置这个值，可以把生成的代理类，输出出来。 中间代理类落到磁盘上去  在项目跟目录生成proxy目录$Proxy0.class
        System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

        /**
         * 概念
         * reflect 反射
         * 对象.getClass() 通过对象找到对应的class
         *               .getClassLoader()  获取到哪个类加载器加载的。
         * 反射不需要知道类的具体细节，通过获取类的二进制字节码就可以知道类的结构。
         * getClass class 获取类的字节码。在反射返回类上可以通过方法属性获取细节。 【java的class文件都叫字节码】
         *
         * Proxy.newProxyInstance 创建代理对象只是在内存中创建proxy0 = 类似 LogProxy TimeProxy....
         * 类文件然后用这个中间代理类创建返回对象，这个类可以通过配置落到磁盘。
         *
         * new Proxy.newProxyInstance 返回的就是代理对象 默认类被隐藏了。类是动态生成的。对象是动态返回的。
         *
         * @parms Tank.class.getClassLoader() 用哪个classLoader把创建的代理对象load到内存 类加载器一般是被代理类的。可以是被代理类的父加载加在的。不能是平行加载器加载。【bootstrap】【ext】【App】【your self custom】 classloader
         * 因为是动态创建的类和对象并没有在内存中所以要手动load进来。
         * 这里与，被代理对象用一个行了。
         * @param new Class[]{Move.class}
         * 被代理对象 用代理所实现的接口类 可以是多个 new Class[]{Move.class}。 代理对象应该实现哪些接口。
         * @param InvocationHandler 真正的代理需要实现的拦截方法。 invoke去调用代理中间类的实现接口的方法。 【实现了InvocationHandler的类对象】
         *
         * 流程
         * m.move() 调用  invoke（如何调用到了Proxy.newProxyInstance上的 invoke?） -> method.invoke() -> 调用中间代理对象的move方法。-> 到被代理类的move方法。
         *
         *
         * 如何调用到了Proxy.newProxyInstance上的 invoke?
         * 分析$Proxy0.class
         * 1. class $Proxy0 extends Proxy implements Move { 实现了move接口
         * 2. 构造 new 对象的时候传入了实现了InvocationHandler的对象。
         * 3. move 方法执行了 实现了InvocationHandler的对象的invoke 方法。  super.h =  实现了InvocationHandler的对象
         */
        Move m = (Move) Proxy.newProxyInstance(Tank.class.getClassLoader(), new Class[]{Move.class}, new InvocationHandler() {
            /**
             *
             * @param proxy   代理对象 getClass().getMethods() // 获取所有的方法集合。
             * @param method  哪个方法被调用的时候的和那个方法有关的对象。 这个相当于getMethods()的被调用方法的指定方法返回。
             * @param args    调用方法的参数。
             * @return method.invoke 相当于 js的  method.apply 作用于哪个对象。上执行 只是tank必须有move方法否则报错,和js略有区别。
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method.getName());
                /**
                 * @param 1 被代理对象
                 * @param 2 调用方法所需要的参数。
                 */
                return method.invoke(new Tank(), args); // move 返回的void实际就是null
            }
        });

        m.move();
    }
}
